package net.jalbum.editor;

import edu.stanford.ejalbert.BrowserLauncher;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSeparator;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;
import net.jalbum.io.PCWriter;
import org.fife.io.UnicodeReader;
import org.fife.ui.autocomplete.AbstractCompletionProvider;
import org.fife.ui.autocomplete.AutoCompletion;
import org.fife.ui.autocomplete.Completion;
import org.fife.ui.autocomplete.DefaultCompletionProvider;
import org.fife.ui.autocomplete.ExternalURLHandler;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import se.datadosen.component.JPlainButton;
import se.datadosen.component.JStackLabel;
import se.datadosen.component.ToggleAction;
import se.datadosen.jalbum.Config;
import se.datadosen.util.IO;
import se.datadosen.util.Platform;
import se.datadosen.util.Stopwatch;
import se.datadosen.util.StringCodec;

/**
 *
 * @author david
 */
public class JTextPadDocument extends JPanel {

    private JTextPad owner;
    private JTabComponent tabComponent = new JTabComponent();
    static final int menuShortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); // Convenience
    /**
     * Mapping between file extension and syntax style
     */
    JMenuBar menuBar;
    JMenu fileMenu;
    JMenu editMenu;
    JMenu tabsMenu;
    JMenu lineTerminationMenu;
    JMenu viewMenu;
    JMenu helpMenu;
    JRadioButtonMenuItem pcTerminationStyle = new JRadioButtonMenuItem("PC style");
    JRadioButtonMenuItem unixTerminationStyle = new JRadioButtonMenuItem("UNIX style");
    ButtonGroup terminationStyle = new ButtonGroup();

    {
        terminationStyle.add(pcTerminationStyle);
        terminationStyle.add(unixTerminationStyle);
        pcTerminationStyle.setSelected(true);
    }
    private RSyntaxTextArea textArea = new RSyntaxTextArea(60, 120) {

        @Override
        protected void paintComponent(Graphics g2) {
            Graphics2D g = (Graphics2D) g2;
            Map hints = (Map) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
            g.setRenderingHints(hints);
//            g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
            super.paintComponent(g);
        }
    };
    private AutoCompletion autoCompletion;
    private DefaultCompletionProvider provider = new DefaultCompletionProvider();
    private RTextScrollPane textScroller;
    private JStackLabel statusBar = new JStackLabel();
    private JFileChooser fileChooser = new JFileChooser();
    private PrinterJob printerJob = PrinterJob.getPrinterJob();    // Handle change detection
    int textHash = 0;

    {
        tabComponent.getCloseButton().addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                    owner.closeDocument(JTextPadDocument.this);
                } catch (OperationCanceledException ex) {
                }
            }
        });

    }
    Action undoAction = RSyntaxTextArea.getAction(RSyntaxTextArea.UNDO_ACTION);
    /**
     * File Actions
     */
    Action newWindowAction = new AbstractAction("New window") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            JTextPad editor = new JTextPad();
            Point rel = owner.getLocationOnScreen();
            editor.setLocation(rel.x + 50, rel.y + 50);
            editor.openDocument(null);
        }
    };
    Action newTabAction = new AbstractAction("New tab") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_T, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            JTextPadDocument document = owner.openDocument(null);
            document.setCurrentDirectory(getCurrentDirectory());
            owner.setCurrentDocument(document);
        }
    };
    Action openAction = new AbstractAction("Open...") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            chooser.setCurrentDirectory(fileChooser.getCurrentDirectory());
            chooser.setSelectedFile(fileChooser.getSelectedFile());
            chooser.setMultiSelectionEnabled(true);
            int choice = chooser.showOpenDialog(owner);
            File[] files = chooser.getSelectedFiles();
            if (choice == JFileChooser.APPROVE_OPTION) {
                boolean first = true;
                for (File f : files) {
                    JTextPadDocument document = owner.openDocument(f);
                    if (first) {
                        owner.setCurrentDocument(document);
                        first = false;
                    }
                }
            }
        }
    };
    Action closeAction = new AbstractAction("Close") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_W, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            try {
                owner.closeDocument(JTextPadDocument.this);
            } catch (OperationCanceledException ex) {
            }
        }
    };
    Action saveAction = new AbstractAction("Save") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            File file = getFile();
            if (file == null) {
                if (e == null) { // Called from SaveAll
                    owner.setCurrentDocument(JTextPadDocument.this);
                }
                saveAsAction.actionPerformed(e);
            } else {
                saveFile(file);
            }
        }
    };
    Action saveAsAction = new AbstractAction("Save As...") {

        public void actionPerformed(ActionEvent e) {

            int choice = fileChooser.showSaveDialog(owner);
            File selectedFile = fileChooser.getSelectedFile();
            if (choice == JFileChooser.APPROVE_OPTION && selectedFile != null) {
                if (selectedFile.exists()) {
                    int answer = JOptionPane.showConfirmDialog(owner,
                            "" + selectedFile.getAbsolutePath() + " already exists.\nDo you wish to replace it?",
                            "Save As",
                            JOptionPane.YES_NO_CANCEL_OPTION,
                            JOptionPane.WARNING_MESSAGE);
                    if (answer == JOptionPane.YES_OPTION) {
                        saveFile(selectedFile);
                        return;
                    }
                    if (answer == JOptionPane.NO_OPTION) {
                        return;
                    }
                }
                saveFile(selectedFile);
                return;
            }
            if (e == null) {
                throw new OperationCanceledException();
            }
        }
    };
    Action pageSetupAction = new AbstractAction("Page Setup...") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_P, menuShortcutKeyMask | KeyEvent.SHIFT_MASK));
        }

        public void actionPerformed(ActionEvent e) {
            PageFormat pf = printerJob.pageDialog(printerJob.defaultPage());
            printerJob.setPrintable(textArea, pf);

        }
    };
    Action printAction = new AbstractAction("Print...") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_P, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            boolean doPrint = printerJob.printDialog();
            if (doPrint) {
                try {
                    printerJob.print();
                } catch (PrinterException ex) {
                    JOptionPane.showMessageDialog(owner,
                            "An error occured during printing: " + ex.getMessage(),
                            JTextPad.APP_NAME,
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        }
    };
    /**
     * Edit actions
     */
    Action goToAction = new AbstractAction("Go To...") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_G, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            String s = JOptionPane.showInputDialog(owner, "Line Number:", "Goto line", JOptionPane.PLAIN_MESSAGE);
            int line = Integer.parseInt(s) - 1;
            int i;
            try {
                i = textArea.getLineStartOffset(line);
                textArea.setCaretPosition(i);
            } catch (BadLocationException ex) {
            }
        }
    };
    Action completeCodeAction = new AbstractAction("Complete Code") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.CTRL_MASK));
        }

        public void actionPerformed(ActionEvent e) {
            autoCompletion.doCompletion(); // triggers nullpointer exception
/*
            try {
            Robot r = new Robot();
            r.keyPress(KeyEvent.VK_CONTROL);
            r.keyPress(KeyEvent.VK_SPACE);
            r.keyRelease(KeyEvent.VK_SPACE);
            r.keyRelease(KeyEvent.VK_CONTROL);
            } catch (AWTException ex) {
            ex.printStackTrace(System.err);
            }
             */
        }
    };
    /**
     * Tabs sub menu
     */
    Action tabSizeAction = new AbstractAction("Set tab size") {

        public void actionPerformed(ActionEvent e) {
            String s = JOptionPane.showInputDialog(owner, "Tab size:", "" + textArea.getTabSize());
            int tabSize = Integer.parseInt(s);
            textArea.setTabSize(tabSize);
        }
    };
    ToggleAction emulatedTabs = new ToggleAction("Emulate tabs with spaces") {

        public void actionPerformed(ActionEvent e) {
            textArea.setTabsEmulated(isSelected());
        }
    };
    Action convertTabsToSpaces = new ToggleAction("Convert tabs to spaces") {

        public void actionPerformed(ActionEvent e) {
            textArea.convertTabsToSpaces();
        }
    };
    Action convertSpacesToTabs = new ToggleAction("Convert spaces to tabs") {

        public void actionPerformed(ActionEvent e) {
            textArea.convertSpacesToTabs();
        }
    };
    /**
     * View actions
     */
    ToggleAction lineWrapAction = new ToggleAction("Line Wrap") {

        {
            setSelected(true);
        }

        public void actionPerformed(ActionEvent e) {
            textArea.setLineWrap(isSelected());
        }
    };
    ToggleAction lineNumbersAction = new ToggleAction("Line Numbers") {

        {
            setSelected(true);
        }

        public void actionPerformed(ActionEvent e) {
            textScroller.setLineNumbersEnabled(isSelected());
        }
    };
    ToggleAction whiteSpaceAction = new ToggleAction("White Space") {

        public void actionPerformed(ActionEvent e) {
            textArea.setWhitespaceVisible(isSelected());
        }
    };
    ToggleAction highlightCurrentLineAction = new ToggleAction("Highlight current line") {

        {
            setSelected(true);
        }

        public void actionPerformed(ActionEvent e) {
            textArea.setHighlightCurrentLine(isSelected());
        }
    };
    Action fontSizeAction = new AbstractAction("Set font size...") {

        public void actionPerformed(ActionEvent e) {
            String s = JOptionPane.showInputDialog(owner, "Font size:", "" + textArea.getFont().getSize());
            float fontSize = Float.parseFloat(s);
            textArea.setFont(textArea.getFont().deriveFont(fontSize));
            Preferences.userNodeForPackage(JTextPadDocument.class).putFloat("fontSize", fontSize);
        }
    };
    /**
     * Help Actions
     */
    Action aboutAction = new AbstractAction("About " + JTextPad.APP_NAME) {

        public void actionPerformed(ActionEvent e) {
            JPanel panel = new JPanel();
            panel.setLayout(new GridLayout(0, 1));
            panel.add(new JLabel(JTextPad.APP_NAME + " " + JTextPad.VERSION));
            panel.add(new JLabel("By David Ekholm, Jalbum"));
            panel.add(new JLabel("Thanks to Robert Futrell for providing the excellent"));
            panel.add(new JLabel("text editor engine RSyntaxTextArea!"));
            JOptionPane.showMessageDialog(owner, panel, "About " + JTextPad.APP_NAME, JOptionPane.INFORMATION_MESSAGE);
        }
    };

    public AbstractCompletionProvider getCompletionProvider() {
        return provider;
    }

    JTextPad getOwner() {
        return owner;
    }

    public JTextPadDocument(JTextPad owner) {
        init(owner);
    }

    public JTextPadDocument(JTextPad owner, File file) {
        init(owner);

        if (file != null) {
            if (file.isDirectory()) {
                setCurrentDirectory(file);
            } else {
                setCurrentDirectory(file.getParentFile());
                openFile(file);
            }
        }
    }

    public JTabComponent getTabComponent() {
        return tabComponent;
    }

    JMenuBar getMenuBar() {
        if (menuBar == null) {
            createMenuBar();
        }
        return menuBar;
    }

    JStackLabel getStatusBar() {
        return statusBar;
    }

    RSyntaxTextArea getTextArea() {
        return textArea;
    }

    void openFile(File file) {
        try {
            byte[] raw = IO.readBytes(file);
            StringCodec sc = new StringCodec();
            String tmp = sc.decode(raw);
            if (tmp.indexOf('\r') >= 0) {
                pcTerminationStyle.setSelected(true);
            } else {
                unixTerminationStyle.setSelected(true);
            }

            String encoding = sc.getDetectedEncoding();
//            System.out.println("Encoding detected as " + encoding);
//            String encoding = StringCodec.isUTF8(raw) ? "UTF-8" : System.getProperty("file.encoding");
            StringBuilder buf = new StringBuilder();
            BufferedReader reader = new BufferedReader(new UnicodeReader(new ByteArrayInputStream(raw), encoding));
            String line;

            while ((line = reader.readLine()) != null) {
                buf.append(line);
                buf.append('\n');
            }

            String text = buf.toString();
            fileChooser.setSelectedFile(file);
            owner.getRootPane().putClientProperty("Window.documentFile", file); // Mac OS X
            setSyntaxStyle();

            textArea.setText(text);
            textArea.setCaretPosition(0);
            textArea.discardAllEdits();
            owner.getRootPane().putClientProperty("Window.documentModified", Boolean.FALSE);
            tabComponent.setChanged(false);

            textHash =
                    text.hashCode();
            updateTitle();

        } catch (IOException ex) {
            JOptionPane.showMessageDialog(owner,
                    "Can't open file " + file.getName() + ": " + ex.getMessage(),
                    JTextPad.APP_NAME,
                    JOptionPane.ERROR_MESSAGE);
        }

    }

    private void writeTextFile(String content, File file, String encoding)
            throws IOException {
        Writer writer = new OutputStreamWriter(new FileOutputStream(file), encoding);
        if (pcTerminationStyle.isSelected()) {
            writer = new PCWriter(writer);
        }

        writer.write(content);
        writer.close();
    }

    void saveFile(File file) {
        try {
            File bundledSkinsDir = Config.getConfig().chainedSkinsDir.getBackgroundDirectory();
            String text = textArea.getText();
            writeTextFile(text, file, "UTF-8");
            textHash =
                    text.hashCode();
            owner.getRootPane().putClientProperty("Window.documentModified", Boolean.FALSE);
            tabComponent.setChanged(false);
            fileChooser.setSelectedFile(file);
            setSyntaxStyle();

            updateTitle();

            statusBar.setText("Document written to " + file.getAbsolutePath());
            owner.getRootPane().putClientProperty("Window.documentFile", file); // Mac OS X
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(owner,
                    "Can't write to file " + file.getName() + ": " + ex.getMessage(),
                    JTextPad.APP_NAME,
                    JOptionPane.ERROR_MESSAGE);
        }

    }

    void ensureNoLoss() throws OperationCanceledException {
        int currentTextHash = textArea.getText().hashCode();
        if (currentTextHash != textHash) {
            owner.setCurrentDocument(this);
            owner.setVisible(true);
            int answer = JOptionPane.showConfirmDialog(owner, "Save changes to " + getTitle() + "?",
                    JTextPad.APP_NAME,
                    JOptionPane.YES_NO_CANCEL_OPTION,
                    JOptionPane.WARNING_MESSAGE);
            if (answer == JOptionPane.NO_OPTION) {
                owner.setVisible(false);
                return;
            } else if (answer == JOptionPane.YES_OPTION) {
                saveAction.actionPerformed(null);
                owner.setVisible(false);
            } else {
                throw new OperationCanceledException();
            }
        }
    }

    public File getCurrentDirectory() {
        return fileChooser.getCurrentDirectory();
    }

    public void setCurrentDirectory(File dir) {
        fileChooser.setCurrentDirectory(dir);
    }

    public void setSyntaxStyle() {
        String ext = IO.extensionOf(getFile()).toLowerCase();
        String syntaxStyle = JTextPad.fileTypes.get(ext);
        if (syntaxStyle == null) {
            syntaxStyle = SyntaxConstants.SYNTAX_STYLE_NONE;
        }

        textArea.setSyntaxEditingStyle(syntaxStyle);
        updateStatusBar();

    }

    public File getFile() {
        return fileChooser.getSelectedFile();
    }

    public String getTitle() {
        File file = getFile();
        if (file == null) {
            return "Untitled";
        }

        return file.getName();
    }

    private void updateStatusBar() {
        int line = textArea.getCaretLineNumber() + 1;
        int col = textArea.getCaretOffsetFromLineStart() + 1;
        String style = pcTerminationStyle.isSelected() ? "PC" : "UNIX";
        statusBar.setText("Mode " + textArea.getSyntaxEditingStyle() + " | UTF-8 | " + style + " | Ln " + line + ", Col " + col);
    }

    private void init(final JTextPad owner) {
        this.owner = owner;

        setLayout(new BorderLayout());

        printerJob.setPrintable(textArea);
        /*
        printerJob.setPrintable(new Printable() {
        
        public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException {
        if (pageIndex > 0) { // We have only one page, and 'page' is zero-based
        return NO_SUCH_PAGE;
        }
        
        // User (0,0) is typically outside the imageable area, so we must
        // translate by the X and Y values in the PageFormat to avoid clipping
        Graphics2D g2d = (Graphics2D)g;
        g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
        
        // Now we perform our rendering
        g.drawString("Hello world!", 100, 100);
        
        // tell the caller that this page is part of the printed document
        return PAGE_EXISTS;
        }
        
        });
         */
        updateTitle();

// Menu bar init was here
        {
            ActionListener terminationStyleListner = new ActionListener() {

                public void actionPerformed(ActionEvent e) {
                    updateStatusBar();
                }
            };
            pcTerminationStyle.addActionListener(terminationStyleListner);
            unixTerminationStyle.addActionListener(terminationStyleListner);
        }

// Setup text component
        textScroller = new RTextScrollPane(textArea);
//        textScroller = new RTextScrollPane(800, 500, textArea, true);

//        RenderingHints hints = (RenderingHints)Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
//        for (Entry<Object, Object> e : hints.entrySet()) {
//            System.out.println("Key:" + e.getKey() + "\t Value:" + e.getValue());
//        }
//        if (hints.containsKey(RenderingHints.KEY_TEXT_ANTIALIASING)) {
//            System.out.println("Has antialiasing rendering hint");
//        }
//        if (hints.containsValue(RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT)) {
//            System.out.println("Has default antialiasing value");
//        }
//        if (hints.containsValue(RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB)) {
//            System.out.println("Has LCD antialiasing value");
//        }

        if (Platform.isWindows()) {
//            textArea.setTextAntiAliasHint("VALUE_TEXT_ANTIALIAS_LCD_HRGB");
            textArea.setFont(new Font("Lucida Sans Typewriter", Font.PLAIN, 13));
        }

        float fontSize = Preferences.userNodeForPackage(JTextPadDocument.class).getFloat("fontSize", 13);
        textArea.setFont(textArea.getFont().deriveFont(fontSize));

        textArea.restoreDefaultSyntaxScheme();
        textArea.setSyntaxEditingStyle(RSyntaxTextArea.SYNTAX_STYLE_NONE);
        textArea.setLineWrap(true);

        // Fix to make shift-space behave like space (instead of being dead)
        KeyStroke shiftSpace = KeyStroke.getKeyStroke("shift SPACE");
        textArea.getInputMap().put(shiftSpace, "insert-space");
        textArea.getActionMap().put("insert-space", new AbstractAction() {

            public void actionPerformed(ActionEvent e) {
                try {
                    textArea.getDocument().insertString(textArea.getCaretPosition(), " ", null);
                } catch (BadLocationException ex) {
                }
            }
        });


        provider.setParent(owner.getCompletionProvider());

        autoCompletion =
                new AutoCompletion(provider) {

                    @Override
                    protected String getReplacementText(
                            Completion c, Document doc, int start, int len) {
                        try {
                            if (start >= 2 && "${".equals(doc.getText(start - 2, 2)) && !endsWithCurlyBrace(doc, start)) {
                                return c.getReplacementText() + "}";
                            } else {
                                return super.getReplacementText(c, doc, start, len);
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                            return null;
                        }

                    }
                };

        autoCompletion.setShowDescWindow(true);
        autoCompletion.setDescriptionWindowSize(400, 400);
        autoCompletion.setExternalURLHandler(new ExternalURLHandler() {

            public void urlClicked(URL url) {
                System.out.println("URL clicked: " + url.toString());
                try {
                    BrowserLauncher.openURL(url.toString());
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(owner,
                            "Can't open URL: " + ex.getMessage(),
                            JTextPad.APP_NAME,
                            JOptionPane.ERROR_MESSAGE);
                }

            }
        });

        autoCompletion.install(textArea);

        textArea.addCaretListener(new CaretListener() {

            public void caretUpdate(CaretEvent e) {
                updateStatusBar();
            }
        });

        textArea.getDocument().addDocumentListener(new DocumentListener() {

            int changeCount = 0;

            public void insertUpdate(DocumentEvent e) {
                warnCheck();
                tabComponent.setChanged(true);
            }

            public void removeUpdate(DocumentEvent e) {
                warnCheck();
                tabComponent.setChanged(true);
            }

            public void changedUpdate(DocumentEvent e) {
                warnCheck();
                tabComponent.setChanged(true);
            }

            private void warnCheck() {
                if (changeCount++ == 2) {
                    File bundledSkinsDir = Config.getConfig().chainedSkinsDir.getBackgroundDirectory();
                    File file = getFile();
                    if (file != null && IO.isSubdirectoryOf(file, bundledSkinsDir)) {
                        SwingUtilities.invokeLater(new Runnable() {

                            public void run() {
                                JOptionPane.showMessageDialog(JTextPadDocument.this,
                                        "You are modifying a bundled skin.\n"
                                        + "Your changes will be lost when updating Album.\n"
                                        + "Please use \"Tools->Skin developer->Create new skin\"\n"
                                        + "to modify a copy instead.", "Modifying bundled files", JOptionPane.WARNING_MESSAGE);
                            }
                        });
                    }
                }
            }
        });

        add(textScroller, BorderLayout.CENTER);
        JPanel southPanel = new JPanel(new BorderLayout());
        southPanel.setBorder(BorderFactory.createEmptyBorder(0, 6, 2, 6));
        southPanel.add(statusBar, BorderLayout.BEFORE_LINE_BEGINS);
        southPanel.add(new JLabel(" "), BorderLayout.AFTER_LINE_ENDS);
        statusBar.setText("no text");
        add(southPanel, BorderLayout.SOUTH);

        undoAction.addPropertyChangeListener(new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent evt) {
                if (owner.getCurrentDocument() != JTextPadDocument.this) {
                    return;
                }
                if ("enabled".equals(evt.getPropertyName())) {
                    int currentTextHash = textArea.getText().hashCode();
                    boolean changed = currentTextHash != textHash;
//                    boolean changed = undoAction.isEnabled();
                    owner.getRootPane().putClientProperty("Window.documentModified", "" + changed);
                    tabComponent.setChanged(changed);
                }
            }
        });
    }

    /**
     * Tell if the current code section is in a variable ending with a }
     */
    private boolean endsWithCurlyBrace(Document doc, int offset) throws BadLocationException {
        int len = doc.getLength();
        int remaining = len - offset;
        // Speed up a bit. No identifier is possibly larger than 100 characters
        if (remaining > 100) {
            remaining = 100;
        }

        String text = doc.getText(offset, remaining);
        char[] chars = text.toCharArray();
        for (char c : chars) {
            if (c == '}') {
                return true;
            }

            if (!Character.isJavaIdentifierPart(c)) {
                return false;
            }

        }
        return false;
    }

    private void createMenuBar() {
        // Menus
        menuBar = new JMenuBar();
        fileMenu =
                new JMenu("File");
        editMenu =
                new JMenu("Edit");
        tabsMenu =
                new JMenu("Tabs");
        lineTerminationMenu =
                new JMenu("Line termination");
        viewMenu =
                new JMenu("View");
        helpMenu =
                new JMenu("Help");

        fileMenu.add(new JMenuItem(newWindowAction));
        fileMenu.add(new JMenuItem(newTabAction));
        fileMenu.add(new JMenuItem(openAction));
        fileMenu.add(new JSeparator());
        fileMenu.add(new JMenuItem(closeAction));
        fileMenu.add(new JSeparator());
        fileMenu.add(new JMenuItem(saveAction));
        fileMenu.add(new JMenuItem(saveAsAction));
        fileMenu.add(new JMenuItem(owner.saveAllAction));
        fileMenu.add(new JSeparator());
        fileMenu.add(new JMenuItem(pageSetupAction));
        fileMenu.add(new JMenuItem(printAction));
        fileMenu.add(new JSeparator());
        fileMenu.add(new JMenuItem(owner.exitAction));
        editMenu.add(new JMenuItem(RSyntaxTextArea.getAction(RSyntaxTextArea.UNDO_ACTION)));
        editMenu.add(new JMenuItem(RSyntaxTextArea.getAction(RSyntaxTextArea.REDO_ACTION)));
        editMenu.add(new JSeparator());
        editMenu.add(new JMenuItem(RSyntaxTextArea.getAction(RSyntaxTextArea.CUT_ACTION)));
        editMenu.add(new JMenuItem(RSyntaxTextArea.getAction(RSyntaxTextArea.COPY_ACTION)));
        editMenu.add(new JMenuItem(RSyntaxTextArea.getAction(RSyntaxTextArea.PASTE_ACTION)));
        editMenu.add(new JSeparator());
        editMenu.add(new JMenuItem(owner.findAction));
        editMenu.add(new JMenuItem(owner.findNextAction));
        editMenu.add(new JMenuItem(owner.replaceAction));
        editMenu.add(new JMenuItem(goToAction));
        editMenu.add(new JSeparator());

        tabsMenu.add(tabSizeAction);
        tabsMenu.add(emulatedTabs.createJCheckBoxMenuItem());
        tabsMenu.add(convertTabsToSpaces);
        tabsMenu.add(convertSpacesToTabs);
        editMenu.add(tabsMenu);

        lineTerminationMenu.add(pcTerminationStyle);
        lineTerminationMenu.add(unixTerminationStyle);
        editMenu.add(lineTerminationMenu);
        editMenu.add(new JSeparator());
        editMenu.add(new JMenuItem(completeCodeAction));
        editMenu.add(new JMenuItem(RSyntaxTextArea.getAction(RSyntaxTextArea.SELECT_ALL_ACTION)));

        viewMenu.add(lineWrapAction.createJCheckBoxMenuItem());
        viewMenu.add(lineNumbersAction.createJCheckBoxMenuItem());
        viewMenu.add(whiteSpaceAction.createJCheckBoxMenuItem());
        viewMenu.add(highlightCurrentLineAction.createJCheckBoxMenuItem());
        viewMenu.add(fontSizeAction);
        viewMenu.addSeparator();
        viewMenu.add(owner.previousDocumentAction);
        viewMenu.add(owner.nextDocumentAction);

        helpMenu.add(new JMenuItem(aboutAction));

        menuBar.add(fileMenu);
        menuBar.add(editMenu);
        menuBar.add(viewMenu);
        if (!Main.isMac() || !Main.runsStandalone) {
            menuBar.add(helpMenu);
        }

    }

    private void updateTitle() {
        tabComponent.setTitle(getTitle());
        owner.updateTitle();
    }
}
